package org.ponly.lpf4j.impl;

import org.ponly.common.util.ArrayUtils;
import org.ponly.common.util.AtomicLazyValue;
import org.ponly.common.util.FileUtils;
import org.ponly.common.util.function.Condition;
import org.ponly.lpf4j.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * default plugin manager based file
 *
 * @author vacoor
 */
public class DefaultPluginManager extends AbstractPluginManager implements PluginManager {
    private static final Logger LOG = LoggerFactory.getLogger(DefaultPluginManager.class);
    private static final String DISABLED_PLUGINS_FILENAME = "disabled_plugins.txt";
    private static final AtomicLazyValue<DefaultPluginManager> INSTANCE = new AtomicLazyValue<DefaultPluginManager>() {
        @Override
        protected DefaultPluginManager compute() {
            return new DefaultPluginManager();
        }
    };

    private String configPath;
    private PluginFactory pluginFactory;
    private ExtensionRegistry extensionRegistry;
    private ExtensionFactory extensionFactory;
    private List<String> userDisabledPlugins;                       // 所有用户禁用的插件信息
    private Map<PluginDescriptor, PluginContext> pluginExtensionRegistryMap;

    public static DefaultPluginManager getInstance() {
        return INSTANCE.getValue();
    }

    public DefaultPluginManager() {
        super(new File(System.getProperty("user.home"), ".lpf4j/plugins").getAbsolutePath());
        configPath = new File(System.getProperty("user.home"), ".lpf4j/").getAbsolutePath();
        pluginFactory = new DefaultPluginFactory();
        extensionRegistry = new DefaultExtensionRegistry();
        extensionFactory = new DefaultExtensionFactory();
        pluginExtensionRegistryMap = new ConcurrentHashMap<>();
    }

    @Override
    public boolean hasAnyExtensions(Class<?> extensionPoint) {
        return extensionRegistry.hasAnyExtensions(extensionPoint);
    }

    @Override
    public <E> E getExtension(Class<E> extensionPoint) {
        return extensionRegistry.getExtension(extensionPoint);
    }

    @Override
    public <E> E[] getExtensions(Class<E> extensionPoint) {
        return ArrayUtils.asArray(extensionRegistry.getExtensions(extensionPoint), extensionPoint);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected void doInitializePlugin(PluginDescriptor descriptor) {
        if (pluginExtensionRegistryMap.containsKey(descriptor)) {
            throw new IllegalStateException("Plugin '" + descriptor.getName() + "' already initialized.");
        }

        Plugin plugin = pluginFactory.create(descriptor);
        // 可能只是依赖插件
        if (null != plugin) {
            try {
                plugin.initialize(getPluginRegistry(descriptor, plugin), extensionFactory);

                LOG.info("Plugin '{}' initialized.", descriptor.getName());
            } catch (Exception ex) {
                LOG.error("Plugin '{}' initialized error ({}).", descriptor.getName(), descriptor.getPluginRoot(), ex);
            } catch (IncompatibleClassChangeError ex) {
                LOG.error("Plugin '{}' initialized error ({}).", descriptor.getName(), descriptor.getPluginRoot(), ex);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected void doDestroyPlugin(PluginDescriptor descriptor) {
        LOG.info("Plugin destroy: '{}'", descriptor.getName());
        PluginContext context = pluginExtensionRegistryMap.remove(descriptor);
        if (null != context) {
            context.destroy();
        }
    }

    private PluginContext getPluginRegistry(PluginDescriptor descriptor, Plugin plugin) {
        PluginContext registry = pluginExtensionRegistryMap.get(descriptor);
        if (null != registry || null == plugin) {
            return registry;
        }
        synchronized (this) {
            if (null == registry) {
                registry = new PluginContext(plugin, extensionRegistry);
                pluginExtensionRegistryMap.put(descriptor, registry);
            }
        }
        return registry;
    }

    @Override
    public boolean isUserDisabled(String pluginId) {
        return null != pluginId && getDisabledPlugins().contains(pluginId);
    }

    @Override
    protected Collection<PluginDescriptor> loadBundledPlugins() {
        return null;
    }

    @Override
    protected boolean doEnablePlugin(DefaultPluginDescriptor descriptor, boolean mustUserDisabled) {
        return onEnablePlugin(descriptor.getId()) && super.doEnablePlugin(descriptor, mustUserDisabled);
    }

    @Override
    protected boolean doDisablePlugin(final DefaultPluginDescriptor descriptor, final Condition<String> checker) {
        return super.doDisablePlugin(descriptor, new Condition<String>() {
            @Override
            public boolean value(String pluginId) {
                return onDisablePlugin(pluginId) && checker.value(pluginId);
            }
        });
    }

    public boolean onEnablePlugin(String id) {
        // 不是用户禁用的, 则为已经启动或无法启动的
        List<String> userDisabled = getDisabledPlugins();
        if (userDisabled.remove(id)) {
            try {
                saveDisabledPlugins(userDisabled, false);
                return true;
            } catch (IOException ex) {
            }
        }
        return false;
    }


    public boolean onDisablePlugin(String id) {
        List<String> disabledPlugins = getDisabledPlugins();
        if (disabledPlugins.add(id)) {
            try {
                saveDisabledPlugins(disabledPlugins, false);
                return true;
            } catch (IOException ex) {

            }
        }
        return false;
    }
    /**
     * 获取所有禁用的插件
     *
     * @return
     */
    public List<String> getDisabledPlugins() {
        if (null == userDisabledPlugins) {
            userDisabledPlugins = loadDisabledPlugins(configPath);
        }
        return userDisabledPlugins;
    }

    /**
     * 加载所有禁用的插件
     *
     * @param configPath
     */
    protected List<String> loadDisabledPlugins(String configPath) {
        final List<String> requiredPlugins = Arrays.asList(System.getProperty("ponly.required.plugins.id", "").split(","));
        final List<String> disabledPlugins = new ArrayList<String>();
        final File file = new File(configPath, DISABLED_PLUGINS_FILENAME);

        if (file.isFile()) {
            try {
                BufferedReader reader = new BufferedReader(new FileReader(file));
                try {
                    String id;
                    while (null != (id = reader.readLine())) {
                        id = id.trim();
                        if (!requiredPlugins.contains(id)) {
                            disabledPlugins.add(id);
                        }
                    }
                } finally {
                    reader.close();
                    if (!requiredPlugins.isEmpty()) {
                        savePluginsList(disabledPlugins, false, file);
                    }
                }
            } catch (IOException ignored) {
            }
        }

        return disabledPlugins;
    }

    private void saveDisabledPlugins(Collection<String> ids, boolean append) throws IOException {
        File plugins = new File(configPath, DISABLED_PLUGINS_FILENAME);
        savePluginsList(ids, append, plugins);
        userDisabledPlugins = null;
    }

    public void savePluginsList(Collection<String> ids, boolean append, File plugins) throws IOException {
        if (!FileUtils.createParentDirectory(plugins) || (!plugins.exists() && !plugins.createNewFile()) || !plugins.canWrite()) {
            LOG.warn("Can't create/write file: '{}'", plugins);
            return;
        }
        PrintWriter writer = new PrintWriter(new FileWriter(plugins, append));
        try {
            for (String id : ids) {
                writer.println(id);
            }
        } finally {
            writer.close();
        }
    }

}
